/*

LoongArch架构提供了用于产生时钟中断的恒定频率定时器（以下简称定时器）。
在使用定时器前，需要为其设定1个自减初始值，定时器会按照固定频率进行自减操作，当自减到0时，产生始终中断信号。
通过配置定时器，MaQueOS就会每隔固定的时间收到时钟中断信号，并进行时钟中断处理。
通过写TCFG寄存器中的字段，就可以进行定时器的配置。
TCFG寄存器需要配置的字段包括定时器倒计时自减的初始值InitVal、循环模式控制位Periodic和定时器使能位EN。

1.InitVal：定时器倒计时自减计数的初始值。
硬件将该值左移2位后的值作为最终的定时器的自减初始值。
例如，若将InitVal字段设置为1，则定时器的自减初始值为4，则定时器在自减4次后，产生时钟中断信号。

2.Periodic：循环模式控制位。
若该位为1，则定时器在倒计时自减至0时，在产生时钟中断信号的同时，将定时器的自减初始值重新加载为InitVal字段中的值左移2位后的值，然后在下一个时钟周期重新自减。
若该位为0，则自减至0时，在产生定时器中断信号后停止自减。

3.EN：定时器使能位。
只有当该位为1时，定时器才会进行倒计时自减，并在减至0时，产生定时器中断信号。

实现时钟中断的使能需要配置2个寄存器：ECFG寄存器和CRMD寄存器。
其中，ECFG寄存器用于使能局部中断；CRMD寄存器用于使能全局中断。
LoongArch支持13个中断，每个中断都有一个局部中断使能位，总共13位，分别对应ECFG寄存器的LIE字段中的13位。

*/
#include <xtos.h>

#define CSR_CRMD 0x0 /* CRMD寄存器的地址，与全局中断相关 */
#define CSR_PRMD 0x1
#define CSR_ECFG 0x4 /* ECFG寄存器的地址 */
#define CSR_ESTAT 0x5 /* ESTAT寄存器的地址 */
#define CSR_EENTRY 0xc /* 例外入口地址寄存器的地址 */
#define CSR_TCFG 0x41 /* TCFG的地址 */
#define CSR_TICLR 0x44  /* 向该字段写1表示为清除定时器中断 */
#define CSR_TLBRENTRY 0x88
#define CSR_CRMD_IE (1UL << 2) /* 控制全局中断的比特位 */
#define CSR_PRMD_PPLV (3UL << 0)
#define CSR_TCFG_EN (1UL << 0)  /* EN 定时器使能位 */
#define CSR_TCFG_PER (1UL << 1) /* Periodic 定时器循环模式位 */
#define CSR_TICLR_CLR (1UL << 0)    /* 向CSR_TICLR字段中写1表示清楚定时器中断 */
#define CSR_ECFG_LIE_TI (1UL << 11) /* ECFG寄存器的LIE字段中的第11位，为1时使能定时器中断。 */
#define CSR_ECFG_LIE_HWI0 (1UL << 2) /* ？ */
#define CSR_ESTAT_IS_TI (1UL << 11) /* 为1时为定时器中断 */
#define CSR_ESTAT_IS_HWI0 (1UL << 2)
#define CSR_ESTAT_ECODE (0x3fUL << 16)
#define CC_FREQ 4
#define L7A_SPACE_BASE (0x10000000UL | DMW_MASK)
#define L7A_INT_MASK (L7A_SPACE_BASE + 0x020)
#define L7A_HTMSI_VEC (L7A_SPACE_BASE + 0x200)
#define IOCSR_EXT_IOI_EN 0x1600
#define IOCSR_EXT_IOI_SR 0x1800
#define KEYBOARD_IRQ 3
#define SATA_IRQ 19
#define KEYBOARD_IRQ_HT 0
#define SATA_IRQ_HT 1
#define NR_TIMER 10

struct timer {
	unsigned int alarm;
	struct process *wait;
};

unsigned long jiffies = 0;
struct timer timer_table[NR_TIMER];
extern struct process *current;
int (*syscalls[])() = {
	sys_fork, sys_input, sys_output, sys_exit, sys_pause,
	sys_mount, sys_exe, sys_shmem, sys_timer, sys_open,
	sys_close, sys_read, sys_create, sys_destroy, sys_write,
	sys_sync, sys_outputw};

int sys_timer(int seconds) {
	int i;

	for (i = 0; i < NR_TIMER; i++) {
		if (timer_table[i].alarm == 0) {
			break;
		}
	}

	if (i == NR_TIMER) {
		panic("panic: timer_table[] is empty!\n");
	}

	timer_table[i].alarm = jiffies + seconds;
	timer_table[i].wait = current;
	current->state = TASK_UNINTERRUPTIBLE;
	schedule();
	return 0;
}

/*
 * 定时器中断函数
 */
void timer_interrupt() {
	int i;

	jiffies++;
	for (i = 0; i < NR_TIMER; i++) {
		if (timer_table[i].alarm == jiffies) {
			timer_table[i].alarm = 0;
			timer_table[i].wait->state = TASK_RUNNING;
		}
	}

	if ((--current->counter) > 0) {
		return;
	}

	current->counter = 0;
	if ((read_csr_32(CSR_PRMD) & CSR_PRMD_PPLV) == 0) {
		return;
	}
	schedule();
}

/*
 * 保存好中断现场后，调用do_exception函数对中断进行处理。
 * do_exception函数通过判断中断的类型，调用相应的中断处理程序进行中断处理。
 */
void do_exception() {
	unsigned int estat;
	unsigned long irq;
	unsigned int ecode;

	estat = read_csr_32(CSR_ESTAT); /* 读取ESTAT寄存器的值 */
	ecode = (estat >> 16) & 0x3f;

	if (estat & CSR_ESTAT_ECODE) {
		if (ecode == 1 || ecode == 2 || ecode == 3) {
			do_no_page();
		} else if (ecode == 4) {
			do_wp_page();
		}
	}

    /* 通过查看ESTAT寄存器中IS字段的第11位是否为1，来判断该中断是否为定时器中断 */
	if (estat & CSR_ESTAT_IS_TI) {
		write_csr_32(CSR_TICLR_CLR, CSR_TICLR); /* 用于清除定时器中断标志 */
		timer_interrupt();
	}	

	if (estat & CSR_ESTAT_IS_HWI0) {
		irq = read_iocsr(IOCSR_EXT_IOI_SR);
		if (irq & (1UL << KEYBOARD_IRQ_HT)) {
			keyboard_interrupt();
			write_iocsr(1UL << KEYBOARD_IRQ_HT, IOCSR_EXT_IOI_SR);
		}
		if (irq & (1UL << SATA_IRQ_HT)) {
			disk_interrupt();
			write_iocsr(1UL << SATA_IRQ_HT, IOCSR_EXT_IOI_SR);
		}
	}
}

/*
 * 使能全局初始化
 */
void int_on() {
	unsigned int crmd;

	crmd = read_csr_32(CSR_CRMD);  /* 读取CRMD寄存器的值 */
	write_csr_32(crmd | CSR_CRMD_IE, CSR_CRMD); /* 使能全局中断 */
}

/*
 * 时钟中断初始化的主要工作是对上面进行介绍的定时器进行配置。
 */
void excp_init() {
	unsigned int val;

	val = read_cpucfg(CC_FREQ); /*
                                 * 首先获取定时器所用始终对应的晶振频率(0x5f5e100)，即定时器每秒自减的次数。
                                 * 通过将该频率的值设置为定时器倒计时自减计数的初始值，达到将定时器中断产生的时间间隔设置为1秒的目的。
                                 */

	write_csr_32(CSR_ECFG_LIE_TI | CSR_ECFG_LIE_HWI0, CSR_ECFG); /* 将ECFG寄存器的LIE字段中的第11位设置为1，使能定时器中断。并且？ */
	write_csr_64((unsigned long)val | CSR_TCFG_EN | CSR_TCFG_PER, CSR_TCFG); /* 通过写TCFG寄存器，进行定时器的配置。 */
	write_csr_64((unsigned long)exception_handler, CSR_EENTRY); /* 将中断处理函数的入口地址填入例外入口地址寄存器EENTRY。 */
	write_csr_64((unsigned long)tlb_handler, CSR_TLBRENTRY);

	*(volatile unsigned long *)(L7A_INT_MASK) = ~(0x1UL << KEYBOARD_IRQ | 0x1UL << SATA_IRQ);
	*(volatile unsigned char *)(L7A_HTMSI_VEC + KEYBOARD_IRQ) = KEYBOARD_IRQ_HT;
	*(volatile unsigned char *)(L7A_HTMSI_VEC + SATA_IRQ) = SATA_IRQ_HT;

	write_iocsr((0x1UL << KEYBOARD_IRQ_HT | 0x1UL << SATA_IRQ_HT), IOCSR_EXT_IOI_EN);
}
